﻿
using NewsReader.Helpers;
using NewsReader.Log;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.Web.Syndication;

namespace NewsReader.ViewModels
{
    public sealed class FeedInfo : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private FeedItem _selectedItem;
        private DateTimeOffset _lastUpdated;
        private Uri _uri;
        private bool _isLoaded;
        private bool _isLoading;
        private String _title;
        private String _desc;
        private String _errorDesc;
        private Uri _imageUri;
        private ObservableCollection<object> _feedItems;


        public FeedInfo(String uri, String title, String imageUri, DateTimeOffset lastUpdated)
        {
            _uri = new Uri(uri);
            _title = title;
            if (!string.IsNullOrEmpty(imageUri))
            {
                _imageUri = new Uri(imageUri);
            }
            _feedItems = new ObservableCollection<object>();
            _lastUpdated = lastUpdated;
        }

        public FeedInfo(string title, String uri)
        {
            _title = title;
            _uri = new Uri(uri);
            _feedItems = new ObservableCollection<object>();
        }

        public Uri Uri
        {
            get { return _uri; }
        }

        public Uri ImageUri
        {
            get { return _imageUri; }
            set
            {
                if (this.ViewModelPropertyChanged<Uri>("ImageUri", value, ref _imageUri, PropertyChanged))
                {
                    this.RaisePropertyChanged(PropertyChanged, "ImageUriExists");
                }
            }
        }


        public bool ImageUriExists
        {
            get { return _imageUri != null; }
        }

        public String Error
        {
            get { return _errorDesc; }
            set { this.ViewModelPropertyChanged("Error", value, ref _errorDesc, PropertyChanged); }
        }

        public String Description
        {
            get { return _desc; }
            set { this.ViewModelPropertyChanged("Description", value, ref _desc, PropertyChanged); }
        }

        public String Title
        {
            get { return _title; }
            set { this.ViewModelPropertyChanged("Title", value, ref _title, PropertyChanged); }
        }

        public bool IsLoaded
        {
            get { return _isLoaded; }
            set { this.ViewModelPropertyChanged("IsLoaded", value, ref _isLoaded, PropertyChanged); }
        }

        public bool IsLoading
        {
            get { return _isLoading; }
            set { this.ViewModelPropertyChanged("IsLoading", value, ref _isLoading, PropertyChanged); }
        }

        public DateTimeOffset LastUpdated
        {
            get { return _lastUpdated; }
            set { this.ViewModelPropertyChanged("LastUpdated", value, ref _lastUpdated, PropertyChanged); }
        }

        public IEnumerable<object> FeedItems
        {
            get { return _feedItems; }
        }

        public FeedItem SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                //Only set selected item if it belongs to this feed.
                if (FeedItems.Contains(value))
                {
                    this.ViewModelPropertyChanged("SelectedItem", value, ref _selectedItem, PropertyChanged);
                }
            }
        }

        public async Task LoadNonGUI()
        {
            var client = new SyndicationClient();
            client.BypassCacheOnRetrieve = true;
            try
            {
                var feed = await client.RetrieveFeedAsync(_uri);
                PopulatePropertiesFromFeed(feed);

                var oldestDate = SettingsManager.Instance.GetOldestDateOfArticleSupported();
                foreach (var si in feed.Items)
                {
                    try
                    {
                        if (si.PublishedDate < oldestDate)
                        {
                            Log.Logger.Log.Warn(string.Format("Ignoring article published on {0} it's later than {1}", si.PublishedDate, oldestDate));
                            continue;
                        }

                        var fi = new FeedItem(si);
                        fi.LoadFullText();
                        AddFeedItem(fi);
                    }
                    catch (Exception ex)
                    {
                        Logger.Log.DebugFormat("Exception loading Feed Item {0}", ex);
                    }
                }
                IsLoaded = true;
            }
            catch
            {
                IsLoaded = false;
            }
        }

        public async Task BeginLoad()
        {
            var client = new SyndicationClient();
            client.BypassCacheOnRetrieve = true;
            IsLoading = true;
            Debug.WriteLine(string.Format("Beginning to load URI {0}", _uri));

            var feed = await client.RetrieveFeedAsync(_uri);
            
            PopulatePropertiesFromFeed(feed);
            var sindItems = feed.Items.Select(si => CloneSyndicationItem(si)).ToList();

            await Task.Factory.StartNew(() =>
            {
                try
                {
                    var oldestDate = SettingsManager.Instance.GetOldestDateOfArticleSupported();
                    foreach (var si in sindItems)
                    {
                        if (si.PublishedDate < oldestDate)
                        {
                            Logger.Log.WarnFormat("Ignoring article published on {0} it's later than {1}", si.PublishedDate, oldestDate);
                            continue;
                        }

                        FeedItem fi = FindExistingFeedItemInViewModel(si.Id);
                        try
                        {
                            if (fi == null)
                            {
                                fi = CreateNewFeedItemAndAddToVM(si);
                            }

                            RequestLoadFullTextIfNecessary(fi);
                            SetSelectedItemForGUI(fi);
                        }
                        catch (Exception ex)
                        {
                            Logger.Log.ErrorFormat("Exception loading Feed Item {0}", ex);
                        }
                    }

                    IsLoaded = true;
                    Error = null;
                }
                catch (Exception ex)
                {
                    Error = ex.Message;
                    Logger.Log.ErrorFormat("Error loading feed {0}", ex);
                }
                finally
                {
                    IsLoading = false;
                }
            });
        }

        private static SyndicationItem CloneSyndicationItem(SyndicationItem si)
        {
            var clone =  new SyndicationItem(si.Title.Text, si.Content, si.Links.Count > 0 ? si.Links[0].Uri : null);
            clone.Id = si.Id;
            clone.PublishedDate = si.PublishedDate;
            clone.Summary = si.Summary;

            return clone;
        }

        private static void RequestLoadFullTextIfNecessary(FeedItem fi)
        {
            if (!fi.FullTextLoaded || SettingsManager.Instance.ParserEngineChanged)
            {
                fi.LoadFullTextAsync().ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        Logger.Log.ErrorFormat("LoadFullTextAsync failed. Exception {0}", t.Exception);
                    }
                });
            }
        }

        private void SetSelectedItemForGUI(FeedItem fi)
        {
            RunActionOnGUIThread(() =>
            {
                if (SelectedItem == null)
                {
                    SelectedItem = fi;
                }
            }).ContinueWith(t =>
            {
                if (t.IsFaulted)
                {
                    Logger.Log.ErrorFormat("SetSelectedItemForGUI failed. Exception {0}", t.Exception);
                }
            });
        }

        private void PopulatePropertiesFromFeed(SyndicationFeed feed)
        {
            if (feed.Title != null)
            {
                Title = feed.Title.Text;
            }

            if (feed.Subtitle != null)
            {
                Description = feed.Subtitle.Text;
            }

            ImageUri = feed.ImageUri;
            LastUpdated = feed.LastUpdatedTime;
        }

        private FeedItem CreateNewFeedItemAndAddToVM(SyndicationItem si)
        {
            var fi = new FeedItem(si);
            RunActionOnGUIThread(() =>
            {
                lock (_feedItems)
                {
                    AddFeedItem(fi);
                }
            }).ContinueWith(t =>
            {
                if (t.IsFaulted)
                {
                    Logger.Log.ErrorFormat("CreateNewFeedItemAndAddToVM failed. Exception {0}", t.Exception);
                }
            });

            return fi;
        }

        private FeedItem FindExistingFeedItemInViewModel(string siId)
        {
            lock (_feedItems)
            {
                return (FeedItem)_feedItems.FirstOrDefault(f => ((FeedItem)f).Id == siId);
            }
        }

        private async Task RunActionOnGUIThread(Action act)
        {
            try
            {
                var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
                await dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    new DispatchedHandler(act));
            }
            catch (Exception ex)
            {
                Logger.Log.ErrorFormat("{0}", ex);
            }
        }

        internal void AddFeedItem(FeedItem fi)
        {
            int index = 0;
            int count = _feedItems.Count;
            while (count > index && ((FeedItem)_feedItems[index]).PublishedTime > fi.PublishedTime)
            {
                index++;
            }
            _feedItems.Insert(index, fi);
        }
    }
}